#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/types.h>

#define LED_DRIVER_STR_LEN 64

struct led_driver {
    int major;
    void __iomem *pin_reuse_reg; // 管脚复用寄存器    
    void __iomem *drive_cap_reg; //　驱动能力寄存器    
    void __iomem *in_out_reg; //　输入输出寄存器
    void __iomem *up_down_reg; //　电平拉低拉高寄存器
    unsigned char pin_reuse_reg_size;
    unsigned char drive_cap_reg_size;
    unsigned char in_out_reg_size;
    unsigned char up_down_reg_size;
    char name[LED_DRIVER_STR_LEN];
};

static struct led_driver g_led_driver = {
    .name = "led_driver",
    .pin_reuse_reg_size = 4,
    .drive_cap_reg_size = 4,
    .in_out_reg_size = 4,
    .up_down_reg_size = 4,
};

static int remap_led_driver(struct led_driver *driver)
{
    int ret;
    driver->pin_reuse_reg = ioremap(0xFD5F8020, driver->pin_reuse_reg_size);
    if (!driver->pin_reuse_reg) {
        printk(KERN_ERR "[%s][%d]ioremap pin_reuse_reg failed\n", __func__, __LINE__);
        ret = -ENOMEM;
        goto PIN_REUSE_REG_IOREMAP_ERR;
    }
    driver->drive_cap_reg = ioremap(0xFD5F9020, driver->drive_cap_reg_size);
    if (!driver->drive_cap_reg) {
        printk(KERN_ERR "[%s][%d]ioremap drive_cap_reg failed\n", __func__, __LINE__);
        ret = -ENOMEM;
        goto DRIVER_CAP_REG_IOREMAP_ERR;
    }
    driver->in_out_reg = ioremap(0xFEC20008, driver->in_out_reg_size);
    if (!driver->in_out_reg) {
        printk(KERN_ERR "[%s][%d]ioremap in_out_reg failed\n", __func__, __LINE__);
        ret = -ENOMEM;
        goto IN_OUT_REG_IOREMAP_ERR;
    }
    driver->up_down_reg = ioremap(0xFEC20000, driver->up_down_reg_size);
    if (!driver->up_down_reg) {
        printk(KERN_ERR "[%s][%d]ioremap up_down_reg failed\n", __func__, __LINE__);
        ret = -ENOMEM;
        goto UP_DOWN_REG_IOREMAP_ERR;
    }

    return 0;
UP_DOWN_REG_IOREMAP_ERR:
    iounmap(driver->in_out_reg);
IN_OUT_REG_IOREMAP_ERR:
    iounmap(driver->drive_cap_reg);
DRIVER_CAP_REG_IOREMAP_ERR:
    iounmap(driver->pin_reuse_reg);
PIN_REUSE_REG_IOREMAP_ERR:
    return ret;
}

static void unremap_led_driver(struct led_driver *driver)
{
    iounmap(driver->up_down_reg);
    iounmap(driver->in_out_reg);
    iounmap(driver->drive_cap_reg);
    iounmap(driver->pin_reuse_reg);
}

static ssize_t led_read(struct file *file, char __user *buff, size_t len, loff_t *offset)
{
    return 0;
}

static ssize_t led_write(struct file *file, const char __user *buff, size_t len, loff_t *offset)
{
    struct led_driver *driver = (struct led_driver*)file->private_data;
    char *kbuff = len < KMALLOC_MAX_SIZE ? (char*)kzalloc(len, GFP_KERNEL) : (char*)vzalloc(len);
    unsigned long size_no_copy = copy_from_user(kbuff, buff, len);
    unsigned long data;
    int ret = kstrtoul(kbuff, 10, &data);
    if (ret == -ERANGE) {
        printk(KERN_ERR "[%s][%d]str %s out of range\n", __func__, __LINE__, kbuff);
    } else if (ret == -EINVAL) {
        printk(KERN_ERR "[%s][%d]str %s parsing error\n", __func__, __LINE__, kbuff);
    } else {
        u32 reg = readl(driver->up_down_reg);
        printk("[%s][%d]read reg=0x%x\n", __func__, __LINE__, reg);
        if (data) {
            reg &= ~(0x8 << 0);
            reg |= ((0x8 << 16) | (0x8 << 0));
        } else {
            reg &= ~(0x8 << 0);
            reg |= (0x8 << 16);
        }
        printk("[%s][%d]write reg=0x%x\n", __func__, __LINE__, reg);
        writel(reg, driver->up_down_reg);
    }
    len < KMALLOC_MAX_SIZE ? kfree(kbuff) : vfree(kbuff);
    return len - size_no_copy;
}

static int led_open(struct inode *inode, struct file *file)
{
    int ret;
    u32 reg;
    struct led_driver *driver = NULL;
    file->private_data = &g_led_driver;
    driver = (struct led_driver*)file->private_data;
    ret = remap_led_driver(driver);
    if (ret) {
        printk(KERN_ERR "[%s][%d]remap_led_driver failed\n", __func__, __LINE__);
        return ret;
    }
    /* GPIO1_A3复用为GPIO */
    reg = readl(driver->pin_reuse_reg);
    reg &= ~(0xF000 << 0);
    reg |= ((0xF000 << 16) | (0x0 << 0));
    writel(reg, driver->pin_reuse_reg);

    /* 驱动能力40ohm */
    reg = readl(driver->drive_cap_reg);
    reg &= ~(0x7000 << 0);
    reg |= ((0x7000 << 16) | (0x6000 << 0));
    writel(reg, driver->drive_cap_reg);

    /* GPIO1_A3设置为输出 */
    reg = readl(driver->in_out_reg);
    reg &= ~(0x8 << 0);
    reg |= ((0x8 << 0) | (0x8 << 0));
    writel(reg, driver->in_out_reg);

    /* 关闭led灯 */
    reg = readl(driver->up_down_reg);
    reg &= ~(0x8 << 0);
    reg |= (0x8 << 16);
    writel(reg, driver->up_down_reg);
    return 0;
}

static int led_close(struct inode *inode, struct file *file)
{
    unremap_led_driver((struct led_driver*)file->private_data);
    return 0;
}

static struct file_operations f_ops = {
    .open = led_open,
    .release = led_close,
    .read = led_read,
    .write = led_write,
};

static int __init led_init(void)
{
    g_led_driver.major = register_chrdev(0, g_led_driver.name, &f_ops);
    if (g_led_driver.major <= 0) {
        printk(KERN_ERR "[%s][%d]register_chrdev failed\n", __func__, __LINE__);
        return g_led_driver.major;
    }
    printk("[%s][%d]major:%d\n", __func__, __LINE__, g_led_driver.major);
    return 0;
}

static void __exit led_exit(void)
{
    unregister_chrdev(g_led_driver.major, g_led_driver.name);
    printk("[%s][%d]\n", __func__, __LINE__);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yujie Guo");
MODULE_DESCRIPTION("rk3588-led driver for Learning");